//
//  NSInvocationTest.m
//  Charles
//
//  Created by Charles on 2018/6/5.
//  Copyright © 2018年 charles. All rights reserved.
//

#import "NSInvocationTest.h"


/**
 为了搞清楚NSInvocation 和 performSelector:withObject: 的区别，下次面试不被虐。
 iOS开发中调用方法的方式有两种：performSelector NSInvocation，
 perform相关的函数有一个局限性，其参数不能超过2个，否则要做较麻烦的处理。
 NSInvocation也是一种消息调用的方法，并且它的参数没有限制。
 */
@implementation NSInvocationTest

/**
 官方文档中有明确说明，NSInvocation 对象只能使用其类方法来初始化，不能使用alloc init 方法
 它执行调用之前，需要设置两个方法：setSelector:  和  setArgument:atIndex:
 */

/**
 无参无返回值
 */
+ (void)testInvocation {
    SEL method = @selector(testMethod);
    // 创建一个函数签名，这个签名可以是任意的，但是签名函数的参数数量和调用要一致。
    // 如果selector 是类方法则用 methodSignatureForSelector
    // 如果selector 是实例方法则用 instanceMethodSignatureForSelector
    NSMethodSignature *sign = [self methodSignatureForSelector:method];
    // 注意：如果此处sign为nil 构造NSInvocation的时候回崩溃
    if (sign == nil) {
        NSLog(@"O__O 好像哪里不对了。。。");
    }
    // 通过签名初始化
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign];
    // 设置target
    [invocation setTarget:self];
    // 设置selector
    [invocation setSelector:method];
    // 消息调用
    [invocation invoke];
}

+ (void)testMethod {
    NSLog(@"NSInvocation test!");
}

/**
 多参数无返回值
 */
- (void)testInvocationArguments {
    SEL method = @selector(argumentsTest:argTwo:argThree:);
    NSMethodSignature *sign = [[self class] instanceMethodSignatureForSelector:method];
    
    if (sign == nil) {
        NSLog(@"O__O 好像哪里不对了。。。");
    }
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign];
    [invocation setTarget:self];
    [invocation setSelector:method];
    
    int argOne = 1,argTwo = 2,argThree = 3;
    // 说明：此处设置参数Index 需要从2 开始，因为前两个被selector 和 target 占用，
    // 注意:传入的参数都是地址 如果是oc 对象 也是如此
    [invocation setArgument:&argOne atIndex:2];
    [invocation setArgument:&argTwo atIndex:3];
    [invocation setArgument:&argThree atIndex:4];
    [invocation invoke];
}

- (void)argumentsTest:(int)argOne argTwo:(int)argTwo argThree:(int)argThree {
    NSLog(@"argOne : %d argTwo : %d argThree : %d",argOne,argTwo,argThree);
}

/**
 多参数有返回值
 */
- (void)testInvocationArgumentsReturnValue{
    SEL method = @selector(argumentsTestReturnValue:argTwo:argThree:);
    NSMethodSignature *sign = [[self class]instanceMethodSignatureForSelector:method];
    if (sign == nil) {
        NSLog(@"O__O 好像哪里不对了。。。");
    }
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign];
    // target 和 selector 这么设置也是没毛病的
    NSInvocationTest *testSelf = self;
    [invocation setArgument:&testSelf atIndex:0];
    [invocation setArgument:&method atIndex:1];
    NSString *argOne = @"1";
    NSString *argTwo = @"2";
    NSString *argThree = @"3";
    NSString *returnString = nil;
    [invocation setArgument:&argOne atIndex:2];
    [invocation setArgument:&argTwo atIndex:3];
    [invocation setArgument:&argThree atIndex:4];
    // 设置方法调用的返回值
    [invocation setReturnValue:&returnString];
    [invocation invoke];
    // 取回方法调用的返回值
    [invocation getReturnValue:&returnString];
    NSLog(@"return str is %@",returnString);
    
    // NSInvocation 的其他属性
    // argumentsRetained属性是判断参数是否还存在
    // 调用retainArguments 之前 NO 调用之后Yes
    NSLog(@"%d",invocation.argumentsRetained);
    [invocation retainArguments];
    NSLog(@"%d",invocation.argumentsRetained);

    // 获取参数值
    if (invocation.argumentsRetained) {
        NSString *argThree = nil;
        [invocation getArgument:&argThree atIndex:4];
        NSLog(@"Get Argument is %@",argThree);
    }
    
    // NSMethodSignature 使用
    [self methodSignature:invocation];
}

- (NSString *)argumentsTestReturnValue:(NSString *)argOne argTwo:(NSString *)argTwo argThree:(NSString *)argThree {
    NSLog(@"argOne : %@ argTwo : %@ argThree : %@",argOne,argTwo,argThree);
    NSString *returnString = [NSString stringWithFormat:@"%@ - %@ - %@",argOne,argTwo,argThree];
    NSLog(@"returnString is %@",returnString);
    return returnString;
}

/**
 NSMethodSignature 的使用
 */
- (void)methodSignature:(NSInvocation *)invocation {
    NSMethodSignature *signature = invocation.methodSignature;
    // 获取方法所占字节数
    NSUInteger frameLength = signature.frameLength;
    NSLog(@"frameLength is %ld",frameLength);
    
    // 获取方法返回值所占字节数
    NSUInteger returnLength = signature.methodReturnLength;
    NSLog(@"returnLength is %ld",returnLength);
    
    // 判断方法是否单向
    BOOL isOneWay = signature.isOneway;
    NSLog(@"isOneWay %d",isOneWay);
    // 获取参数个数
    NSInteger argCount = signature.numberOfArguments;
    NSLog(@"arg count is %ld",argCount);
    
    // 打印出所有的参数类型
    // @ 表示 对象；: 表示SEL类型
    for (int i = 0; i < argCount; i++) {
        const char *argType = [signature getArgumentTypeAtIndex:i];
        NSLog(@"参数类型 %s",argType);
    }
    
    // 获取返回值的类型
    const char *returnType = [signature methodReturnType];
    NSLog(@"返回值类型 %s",returnType);
}
@end
